{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "# 自定义工具\n",
    "## 两种自定义方式\n",
    "1. 使用 **@tool装饰器**（自定义工具的最简单方式）\n",
    "\t- 装饰器默认使用 **函数名称作为工具名称**，但可以通过参数 `name_or_callable` 来覆盖此设置。\n",
    "\t- 同时，装饰器将使用函数的 **文档字符串 作为 工具的描述 ，因此函数必须提供文档字符串**。\n",
    "\n",
    "2. 使用 `StructuredTool.from_function` 类方法\n",
    "\t- 这类似于 **@tool 装饰器**，但允许更多配置和同步/异步实现的规范。\n",
    "\n",
    "## 几个常用属性\n",
    "Tool由几个常用属性组成：\n",
    "| 属性 | 类型 | 描述 |\n",
    "|--|--|--|\n",
    "| name | str | **必选的**，在提供给LLM或Agent的工具集中必须是唯一的。 |\n",
    "| description | str | **可选但建议** ，描述工具的功能。LLM或Agent将使用此描述作为上下文，使用它确定工具的使用 |\n",
    "| args_schema | Pydantic BaseModel | **可选但建议**，可用于提供更多信息（例如，few-shot示例）或验证预期参数。 |\n",
    "| return_direct | boolean | 仅对Agent相关。当为True时，在调用给定工具后，Agent将停止并将结果直接返回给用户。 |"
   ],
   "id": "d9e45537ce636e56"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## @tool 装饰器",
   "id": "db2178414823b581"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 举例一、@tool 装饰器简单使用",
   "id": "e22d0fad5ce1af7"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-11-12T07:48:44.978710Z",
     "start_time": "2025-11-12T07:48:44.090031Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import langchain_core.messages\n",
    "from langchain_core.tools import tool\n",
    "\n",
    "@tool\n",
    "def add_number(a:int, b:int) -> int:\n",
    "    \"\"\"两个整数相加\"\"\"\n",
    "    return a + b\n",
    "\n",
    "print(f\"name={add_number.name}\")\n",
    "print(f\"description={add_number.description}\")\n",
    "print(f\"args_schema={add_number.args}\")\n",
    "print(f\"return_direct={add_number.return_direct}\")\n",
    "\n",
    "res = add_number.invoke({\"a\": 10, \"b\": 20})\n",
    "print(f\"result={res}\")"
   ],
   "id": "1be2aad5ff8d6796",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "name=add_number\n",
      "description=两个整数相加\n",
      "args_schema={'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}\n",
      "return_direct=False\n",
      "result=30\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 举例二、@tool 装饰器自定义基础属性",
   "id": "96baec0296fbbb6c"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-11-12T08:08:56.119563Z",
     "start_time": "2025-11-12T08:08:56.108480Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from langchain_core.tools import tool\n",
    "\n",
    "@tool(name_or_callable=\"add_two_number\", description=\"用于两个整数相加\")\n",
    "def add_number(a:int, b:int) -> int:\n",
    "    \"\"\"两个整数相加\"\"\"\n",
    "    return a + b\n",
    "\n",
    "print(f\"name={add_number.name}\")\n",
    "print(f\"description={add_number.description}\")\n",
    "print(f\"args_schema={add_number.args}\")\n",
    "print(f\"return_direct={add_number.return_direct}\")\n",
    "\n",
    "res = add_number.invoke({\"a\": 20, \"b\": 30})\n",
    "print(f\"add_number={res}\")"
   ],
   "id": "8a27e09f079fd3c4",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "name=add_two_number\n",
      "description=用于两个整数相加\n",
      "args_schema={'a': {'title': 'A', 'type': 'integer'}, 'b': {'title': 'B', 'type': 'integer'}}\n",
      "return_direct=False\n",
      "add_number=50\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 举例三、@tool 装饰器自定义参数属性",
   "id": "20c1aa7d1bb81271"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-11-12T08:25:38.398170Z",
     "start_time": "2025-11-12T08:25:38.385116Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from pydantic import BaseModel, Field\n",
    "from langchain_core.tools import tool\n",
    "\n",
    "class FieldInfo(BaseModel):\n",
    "    a :int = Field(description=\"第一个参数\")\n",
    "    b :int = Field(description=\"第二个参数\")\n",
    "\n",
    "@tool(name_or_callable=\"add_two_number\", description=\"用于两个整数相加\", args_schema=FieldInfo, return_direct=True)\n",
    "def add_number(a: int, b: int) -> int:\n",
    "    \"\"\"两个整数相加\"\"\"\n",
    "    return a + b\n",
    "\n",
    "print(f\"name={add_number.name}\")\n",
    "print(f\"description={add_number.description}\")\n",
    "print(f\"args_schema={add_number.args}\")\n",
    "print(f\"return_direct={add_number.return_direct}\")\n",
    "\n",
    "res = add_number.invoke({\"a\": 20, \"b\": 30})\n",
    "print(f\"add_number={res}\")"
   ],
   "id": "2a24b2c2a82e50fd",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "name=add_two_number\n",
      "description=用于两个整数相加\n",
      "args_schema={'a': {'description': '第一个参数', 'title': 'A', 'type': 'integer'}, 'b': {'description': '第二个参数', 'title': 'B', 'type': 'integer'}}\n",
      "return_direct=True\n",
      "add_number=50\n"
     ]
    }
   ],
   "execution_count": 5
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 方式2：StructuredTool的from_function()\n",
    "**StructuredTool.from_function类方法** 提供了比 @tool 装饰器更多的可配置性，而无需太多额外的代码。"
   ],
   "id": "36d0c6e693c0d466"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 举例一、StructuredTool的from_function()简单使用",
   "id": "6fcc8f287483cf59"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-11-12T11:42:17.874443Z",
     "start_time": "2025-11-12T11:42:17.846437Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from langchain_core.tools import StructuredTool\n",
    "\n",
    "def search_function(query: str):\n",
    "    return \"langchain\"\n",
    "\n",
    "\n",
    "search = StructuredTool.from_function(\n",
    "    name=\"search_function\",\n",
    "    description=\"useful for when you want to answer questions about current events\",\n",
    "    func=search_function,\n",
    ")\n",
    "\n",
    "print(f\"name={search.name}\")\n",
    "print(f\"description={search.description}\")\n",
    "print(f\"args_schema={search.args}\")\n",
    "print(f\"return_direct={search.return_direct}\")\n",
    "res = search.invoke({\"query\": \"hello\"})\n",
    "print(f\"search res={res}\")"
   ],
   "id": "7f1aba3f83643a72",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "name=search_function\n",
      "description=useful for when you want to answer questions about current events\n",
      "args_schema={'query': {'title': 'Query', 'type': 'string'}}\n",
      "return_direct=False\n",
      "search res=langchain\n"
     ]
    }
   ],
   "execution_count": 6
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "### 举例二、StructuredTool的from_function()自定义参数属性",
   "id": "fb112537ebaca1b5"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-11-13T01:46:59.503395Z",
     "start_time": "2025-11-13T01:46:59.486390Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class SearchFunction(BaseModel):\n",
    "    query: str = Field(description=\"用户查询\")\n",
    "\n",
    "def search_function(query: str):\n",
    "    return \"langchain\"\n",
    "\n",
    "tools = StructuredTool.from_function(name=\"useful_search\", description=\"useful for when you want to answer questions about current events\", func=search_function, args_schema=SearchFunction)\n",
    "\n",
    "print(f\"name={tools.name}\")\n",
    "print(f\"description={tools.description}\")\n",
    "print(f\"args_schema={tools.args}\")\n",
    "print(f\"return_direct={tools.return_direct}\")\n",
    "res = tools.invoke({\"query\": \"hello\"})\n",
    "print(f\"search res={res}\")"
   ],
   "id": "633a80e597a7d67a",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "name=useful_search\n",
      "description=useful for when you want to answer questions about current events\n",
      "args_schema={'query': {'description': '用户查询', 'title': 'Query', 'type': 'string'}}\n",
      "return_direct=False\n",
      "search res=langchain\n"
     ]
    }
   ],
   "execution_count": 7
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "# 工具调用举例\n",
    "我们通过大模型分析用户需求，判断是否需要调用指定工具。"
   ],
   "id": "ea2373a929513e4e"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 前提：定义大模型",
   "id": "27c5da089ef515c9"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-11-13T02:02:26.852380Z",
     "start_time": "2025-11-13T02:02:25.438546Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import dotenv\n",
    "import os\n",
    "\n",
    "from langchain_openai import ChatOpenAI, OpenAI\n",
    "\n",
    "dotenv.load_dotenv()\n",
    "\n",
    "os.environ[\"OPENAI_API_KEY\"] = os.getenv(\"OPENAI_API_KEY\")\n",
    "os.environ[\"OPENAI_API_BASE\"] = os.getenv(\"OPENAI_BASE_URL\")\n",
    "CHAT_MODEL = ChatOpenAI(\n",
    "    model=\"gpt-4o-mini\",\n",
    "    temperature=0\n",
    ")\n",
    "\n",
    "llm = OpenAI(\n",
    "    model=\"gpt-4o-mini\",\n",
    "    temperature=0\n",
    ")"
   ],
   "id": "6e4d407c98153e3d",
   "outputs": [],
   "execution_count": 8
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 举例1：大模型分析调用工具",
   "id": "2a6e03e64063eb30"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-11-13T02:02:51.087503Z",
     "start_time": "2025-11-13T02:02:49.140102Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from langchain_core.messages import HumanMessage\n",
    "from langchain_core.utils.function_calling import convert_to_openai_function\n",
    "from langchain_community.tools import MoveFileTool\n",
    "\n",
    "# 定义工具\n",
    "tools = [MoveFileTool()]\n",
    "# 这里需要将工具转换为openai函数，后续再将函数传入模型调用\n",
    "functions = [convert_to_openai_function(t) for t in tools]\n",
    "\n",
    "messages = [HumanMessage(content=\"将文件a移动到桌面\")]\n",
    "\n",
    "response = CHAT_MODEL.invoke(input=messages, functions=functions)\n",
    "print(response)"
   ],
   "id": "adca3fb2b5e05b26",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "content='' additional_kwargs={'function_call': {'arguments': '{\"source_path\":\"a\",\"destination_path\":\"/Users/YourUsername/Desktop/a\"}', 'name': 'move_file'}, 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 27, 'prompt_tokens': 76, 'total_tokens': 103, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_efad92c60b', 'id': 'chatcmpl-CbGxdy8oB5v2yCe6SZhVUnCkKI1du', 'service_tier': None, 'finish_reason': 'function_call', 'logprobs': None} id='run--7ad454e6-9363-42ef-8c5b-884e8da7fcdc-0' usage_metadata={'input_tokens': 76, 'output_tokens': 27, 'total_tokens': 103, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}\n"
     ]
    }
   ],
   "execution_count": 10
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 举例2：确定工具并调用",
   "id": "575c24a7408ba94f"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-11-13T02:34:24.466714Z",
     "start_time": "2025-11-13T02:34:23.218348Z"
    }
   },
   "cell_type": "code",
   "source": [
    "import json\n",
    "from langchain_core.utils.function_calling import convert_to_openai_function\n",
    "from langchain_community.tools import MoveFileTool\n",
    "from langchain_core.messages.human import HumanMessage\n",
    "\n",
    "tools = [MoveFileTool()]\n",
    "functions = [convert_to_openai_function(t) for t in tools]\n",
    "messages = [HumanMessage(content=\"将本目录下的abc.txt文件移动到D:\\python-project\\langchain\\temp\")]\n",
    "response = CHAT_MODEL.invoke(input=messages, functions=functions)\n",
    "print(response)\n",
    "\n",
    "print(\"\\n======== 检查是否需要调用工具 ========\")\n",
    "if \"function_call\" in response.additional_kwargs:\n",
    "    tool_name = response.additional_kwargs[\"function_call\"][\"name\"]\n",
    "    tool_args = json.loads(response.additional_kwargs[\"function_call\"][\"arguments\"])\n",
    "    print(f\"需要调用工具：{tool_name}，参数：{tool_args}\")\n",
    "    print(\"\\n======== 开始调用工具 ========\")\n",
    "    if \"move_file\" == response.additional_kwargs[\"function_call\"][\"name\"]:\n",
    "        tool = MoveFileTool()\n",
    "        result = tool.run(tool_args)\n",
    "        print(f\"工具调用结果：{result}\")\n",
    "else:\n",
    "    print(f\"模型回复：{response.content}\")"
   ],
   "id": "c68a6fafccea8e82",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "content='' additional_kwargs={'function_call': {'arguments': '{\"source_path\":\"abc.txt\",\"destination_path\":\"D:\\\\\\\\python-project\\\\\\\\langchain\\\\\\\\temp\\\\\\\\abc.txt\"}', 'name': 'move_file'}, 'refusal': None} response_metadata={'token_usage': {'completion_tokens': 33, 'prompt_tokens': 88, 'total_tokens': 121, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_efad92c60b', 'id': 'chatcmpl-CbHSBYC34CdWYa8IuFr3msm4QVczN', 'service_tier': None, 'finish_reason': 'function_call', 'logprobs': None} id='run--2c79afb9-0be8-47ed-9566-d1381a082981-0' usage_metadata={'input_tokens': 88, 'output_tokens': 33, 'total_tokens': 121, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}\n",
      "\n",
      "======== 检查是否需要调用工具 ========\n",
      "需要调用工具：move_file，参数：{'source_path': 'abc.txt', 'destination_path': 'D:\\\\python-project\\\\langchain\\\\temp\\\\abc.txt'}\n",
      "\n",
      "======== 开始调用工具 ========\n",
      "工具调用结果：File moved successfully from abc.txt to D:\\python-project\\langchain\\temp\\abc.txt.\n"
     ]
    }
   ],
   "execution_count": 18
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
